package ru.efko.dims.domain;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import ru.efko.dims.chart.SensorValueChart;

/**
 * Created with IntelliJ IDEA. User: csulimin Date: 22.09.13 Time: 16:29 To
 * change this template use File | Settings | File Templates.
 */
public class SensorDeviceImpl implements SensorDevice {
	private SocketAddress _address = null;
	private SocketChannel _channel = null;

	private final static Object _connectionSignal = new Object();
	private final static Object _readStartedSignal = new Object();
	private final static Object _parametersChangedSignal = new Object();
	private final static Object _dataChangedSignal = new Object();

	private final Lock _dataLock = new ReentrantLock();
	private final Lock _parametersLock = new ReentrantLock();

	private ByteBuffer _readBuffer = ByteBuffer.allocate(8192);
	private Thread _readThread = null;
	private Thread _pollingThread = null;
	private int[] _parameters = null;
	private int[] _data = null;
	private boolean _stopThreads = true;
	private boolean _stopPolling = true;
	private int _pollInterval = 25;
	private boolean _parametersUpdated = false;
	private static boolean isOnGraphicsActivity = false;
	private static boolean isOnSettingsActivity =true;
	private static Method method;
	private DataChangeHandler _dataHandler = null;
	
	public void setDataChangeHandler(DataChangeHandler dataHandler) {
		_dataHandler = dataHandler;
	}
	public static void setOnGraphicsActivity(boolean isOnGraphicsActivity) {
		SensorDeviceImpl.isOnGraphicsActivity = isOnGraphicsActivity;
	}

	public static void setOnSettingsActivity(boolean isOnSettingsActivity) {
		SensorDeviceImpl.isOnSettingsActivity = isOnSettingsActivity;
	}

	public SensorDeviceImpl() {
	}

	public boolean connect(SocketAddress address) {
		_address = address;
		_stopThreads = false;
		// Connect to device
		if (!checkConnection()) {
			// could not connect to device
			return false;
		}

		// Run read from device thread
		Thread _readThread = new Thread(new Runnable() {
			public void run() {
				readThreadProc();
			}
		});
		synchronized (_readStartedSignal) {
			_readThread.start();
			try {
				_readStartedSignal.wait(1000);
			} catch (InterruptedException e) {
				closeConnection();
				_readThread.isInterrupted();
				_readThread = null;
				return false;
			}
		}

		// try to get initial parameters from device;
		if (!requestParameters(true)) {
			closeConnection();
			try {
				_readThread.join(500);
			} catch (InterruptedException e) {
				_readThread.isInterrupted();
			} finally {
				_readThread = null;
			}
			return false;
		}
		return true;
	}

	public void disconnect() {
		closeConnection();
		if (!_stopPolling) {
			stopPolling();
		}
	}

	private void closeConnection() {
		_stopThreads = true;
		if (_channel != null) {
			try {
				_channel.close();
			} catch (IOException e) {
			} finally {
				_channel = null;
			}
		}
	}

	private boolean checkConnection() {
		if (_stopThreads)
			return false;
		if (_channel != null && _channel.isConnected())
			return true;
		Thread connect = new Thread(new Runnable() {
			public void run() {
				setConnection();
			}
		});
		synchronized (_connectionSignal) {
			connect.start();
			try {
				_connectionSignal.wait(3000);
			} catch (InterruptedException e) {
				connect.interrupt();
				return false;
			}
		}
		return (_channel != null && _channel.isConnected());
	}

	private void setConnection() {
		synchronized (_connectionSignal) {
			while (!_stopThreads
					&& (_channel == null || !_channel.isConnected())) {
				try {
					_channel = SocketChannel.open();
					_channel.connect(_address);
					if (_channel.isConnected()) {
						_connectionSignal.notifyAll();
						return;
					}
				} catch (IOException e) {
					_channel = null;
				}
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					_connectionSignal.notifyAll();
				}
			}
		}
	}

	private void readThreadProc() {
		synchronized (_readStartedSignal) {
			_readStartedSignal.notifyAll();
		}
		while (!_stopThreads && checkConnection()) {
			try {
				// _readBuffer.clear();
				// _readBuffer.flip();
				// int readCount = _channel.read(_readBuffer);
				InputStream stream = Channels.newInputStream(_channel);
				byte[] buffer = new byte[8192];
				int readCount = stream.read(buffer);
				// _readBuffer.flip();
				if (readCount > 0) {
					byte[] data = new byte[readCount];
					data = Arrays.copyOf(buffer, readCount);
					// _readBuffer.get(data, 0, data.length);
					processRead(data);
				}
			} catch (IOException e) {
			}
		}
	}

	private void processRead(byte[] data) {
		CommandData cmd = CommandData.decodeCommand(data);
		if (cmd == null) {
			// log пришла хрень а не команда
			return;
		}

		if (cmd.get_command() == 0x01) {
			synchronized (_parametersChangedSignal) {
				_parametersUpdated = true;
				_parametersChangedSignal.notifyAll();
			}
			// Call handler for changed parameters
		} else if (cmd.get_command() == 0x02) {
			// response on get parameters command
			if (cmd.get_data() != null && cmd.get_data().length == 10) {
				boolean paramsChanged = false;
				if (_parameters != null) {
					for (int i = 0; i < _parameters.length; i++) {
						if (cmd.get_data()[i] != _parameters[i]) {
							paramsChanged = true;
							break;
						}
					}
				} else {
					paramsChanged = true;
				}

				if (paramsChanged) {
					synchronized (_parametersLock) {
						try {
							_parametersLock.lock();
							_parameters = cmd.get_data();
						} finally {
							_parametersLock.unlock();
						}
					}
				}
			}
			synchronized (_parametersChangedSignal) {
				_parametersUpdated = true;
				_parametersChangedSignal.notifyAll();
			}
			// Call handler for changed parameters
		} else if (cmd.get_command() == 0x03) {
			// response on get data command
			synchronized (_dataLock) {
				try {
					_dataLock.lock();
					_data = (cmd.get_data() == null) ? new int[0] : cmd
							.get_data();
				} finally {
					_dataLock.unlock();
				}
			}
			synchronized (_dataChangedSignal) {
				
				_dataChangedSignal.notifyAll();
			}
			// Call handler for changed data
			if (_dataHandler != null) {
				_dataHandler.processNewData();
			}
		}
	}

	public int[] get_parameters() {
		synchronized (_parametersLock) {
			try {
				_parametersLock.lock();
				return _parameters;
			} finally {
				_parametersLock.unlock();
			}
		}
	}

	public void set_parameters(int[] value) {
		if (value != null && value.length == 10) {
			synchronized (_parametersLock) {
				try {
					_parametersLock.lock();
					_parameters = value;
				} finally {
					_parametersLock.unlock();
				}
			}
			sendParameters();
		}
	}

	public boolean requestParameters(boolean sync) {
		_parametersUpdated = false;
		do {
			if (checkConnection()) {
				Thread _sendParamsThread = new Thread(new Runnable() {
					public void run() {
						sendToDevice(CommandData.getParametersFromDevice());
					}
				});
				synchronized (_parametersChangedSignal) {
					_sendParamsThread.start();
					if (sync) {
						try {
							_parametersChangedSignal.wait(50);
							if (_parametersUpdated)
								return true;
						} catch (InterruptedException e) {
						}
					}
				}
			} else {
				return false;
			}
		} while (sync);
		return true;
	}

	public void sendParameters() {

		if (!_stopThreads && checkConnection() && _parameters != null) {
			synchronized (_parametersLock) {
				try {
					_parametersLock.lock();
					Thread _sendParamsThread = new Thread(new Runnable() {
						public void run() {
							sendToDevice(CommandData
									.setParametersToDevice(_parameters));
						}
					});
					_sendParamsThread.start();
				} finally {
					_parametersLock.unlock();
				}
			}
		}
	}

	private void sendToDevice(byte[] data) {
		if (!_stopThreads && checkConnection() && data != null) {
			try {
				_channel.write(ByteBuffer.wrap(data));
			} catch (IOException e) {
			}
		}
	}

	public int[] get_data() {
		synchronized (_dataLock) {
			try {
				_dataLock.lock();
				return _data;
			} finally {
				_dataLock.unlock();
			}
		}
	}

	public void pollData() {
		byte[] cmd = CommandData.getDataToDevice();
		while (!_stopThreads && !_stopPolling) {
			sendToDevice(cmd);
			try {
				Object monitor = new Object();
				synchronized (monitor) {
					monitor.wait(_pollInterval);
				}
			} catch (InterruptedException e) {
				break;
			}
		}
	}

	public void startPolling(int pollingInterval) {
		_pollInterval = pollingInterval;
		_stopPolling = false;
		if (_pollingThread != null){
			return;
		}
		_pollingThread = new Thread(new Runnable() {
			public void run() {
				pollData();
			}
		});
		_pollingThread.start();
	}

	public void stopPolling() {
		_stopPolling = true;
		if (_pollingThread == null){
			return;
		}
		try {
			_pollingThread.join(_pollInterval * 5);
		} catch (InterruptedException e) {
			_pollingThread.interrupt();
		} finally {
			_pollingThread = null;
		}
	}
	
	@Override
	public void setDataHandler(DataChangeHandler dataChangeHandler) {
		_dataHandler = dataChangeHandler;
		
	}
}
